/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: SimpleResultTreeImpl.java,v 1.2.4.1 2005/09/06 10:09:25 pvedula Exp $
 */
package com.sun.org.apache.xalan.internal.xsltc.dom;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.StripFilter;
import com.sun.org.apache.xalan.internal.xsltc.runtime.Hashtable;

import com.sun.org.apache.xml.internal.dtm.DTM;
import com.sun.org.apache.xml.internal.dtm.Axis;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.dtm.DTMAxisTraverser;
import com.sun.org.apache.xml.internal.dtm.DTMManager;
import com.sun.org.apache.xml.internal.dtm.ref.DTMAxisIteratorBase;
import com.sun.org.apache.xml.internal.dtm.ref.DTMManagerDefault;
import com.sun.org.apache.xml.internal.serializer.EmptySerializer;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import com.sun.org.apache.xml.internal.utils.XMLString;
import com.sun.org.apache.xml.internal.utils.XMLStringDefault;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import org.xml.sax.SAXException;

import javax.xml.transform.SourceLocator;

/**
 * This class represents a light-weight DOM model for simple result tree fragment(RTF).
 * A simple RTF is an RTF that has only one Text node. The Text node can be produced by a
 * combination of Text, xsl:value-of and xsl:number instructions. It can also be produced
 * by a control structure (xsl:if or xsl:choose) whose body is pure Text.
 * <p>
 * A SimpleResultTreeImpl has only two nodes, i.e. the ROOT node and its Text child. All DOM
 * interfaces are overridden with this in mind. For example, the getStringValue() interface
 * returns the value of the Text node. This class receives the character data from the
 * characters() interface.
 * <p>
 * This class implements DOM and SerializationHandler. It also implements the DTM interface
 * for support in MultiDOM. The nested iterators (SimpleIterator and SingletonIterator) are
 * used to support the nodeset() extension function.
 */
public class SimpleResultTreeImpl extends EmptySerializer implements DOM, DTM {

  /**
   * The SimpleIterator is designed to support the nodeset() extension function. It has
   * a traversal direction parameter. The DOWN direction is used for child and descendant
   * axes, while the UP direction is used for parent and ancestor axes.
   *
   * This iterator only handles two nodes (RTF_ROOT and RTF_TEXT). If the type is set,
   * it will also match the node type with the given type.
   */
  public final class SimpleIterator extends DTMAxisIteratorBase {

    static final int DIRECTION_UP = 0;
    static final int DIRECTION_DOWN = 1;
    static final int NO_TYPE = -1;

    // The direction of traversal (default to DOWN).
    // DOWN is for child and descendant. UP is for parent and ancestor.
    int _direction = DIRECTION_DOWN;

    int _type = NO_TYPE;
    int _currentNode;

    public SimpleIterator() {
    }

    public SimpleIterator(int direction) {
      _direction = direction;
    }

    public SimpleIterator(int direction, int type) {
      _direction = direction;
      _type = type;
    }

    public int next() {
      // Increase the node ID for down traversal. Also match the node type
      // if the type is given.
      if (_direction == DIRECTION_DOWN) {
        while (_currentNode < NUMBER_OF_NODES) {
          if (_type != NO_TYPE) {
            if ((_currentNode == RTF_ROOT && _type == DTM.ROOT_NODE)
                || (_currentNode == RTF_TEXT && _type == DTM.TEXT_NODE)) {
              return returnNode(getNodeHandle(_currentNode++));
            } else {
              _currentNode++;
            }
          } else {
            return returnNode(getNodeHandle(_currentNode++));
          }
        }

        return END;
      }
      // Decrease the node ID for up traversal.
      else {
        while (_currentNode >= 0) {
          if (_type != NO_TYPE) {
            if ((_currentNode == RTF_ROOT && _type == DTM.ROOT_NODE)
                || (_currentNode == RTF_TEXT && _type == DTM.TEXT_NODE)) {
              return returnNode(getNodeHandle(_currentNode--));
            } else {
              _currentNode--;
            }
          } else {
            return returnNode(getNodeHandle(_currentNode--));
          }
        }

        return END;
      }
    }

    public DTMAxisIterator setStartNode(int nodeHandle) {
      int nodeID = getNodeIdent(nodeHandle);
      _startNode = nodeID;

      // Increase the node ID by 1 if self is not included.
      if (!_includeSelf && nodeID != DTM.NULL) {
        if (_direction == DIRECTION_DOWN) {
          nodeID++;
        } else if (_direction == DIRECTION_UP) {
          nodeID--;
        }
      }

      _currentNode = nodeID;
      return this;
    }

    public void setMark() {
      _markedNode = _currentNode;
    }

    public void gotoMark() {
      _currentNode = _markedNode;
    }

  } // END of SimpleIterator

  /**
   * The SingletonIterator is used for the self axis.
   */
  public final class SingletonIterator extends DTMAxisIteratorBase {

    static final int NO_TYPE = -1;
    int _type = NO_TYPE;
    int _currentNode;

    public SingletonIterator() {
    }

    public SingletonIterator(int type) {
      _type = type;
    }

    public void setMark() {
      _markedNode = _currentNode;
    }

    public void gotoMark() {
      _currentNode = _markedNode;
    }

    public DTMAxisIterator setStartNode(int nodeHandle) {
      _currentNode = _startNode = getNodeIdent(nodeHandle);
      return this;
    }

    public int next() {
      if (_currentNode == END) {
        return END;
      }

      _currentNode = END;

      if (_type != NO_TYPE) {
        if ((_currentNode == RTF_ROOT && _type == DTM.ROOT_NODE)
            || (_currentNode == RTF_TEXT && _type == DTM.TEXT_NODE)) {
          return getNodeHandle(_currentNode);
        }
      } else {
        return getNodeHandle(_currentNode);
      }

      return END;
    }

  }  // END of SingletonIterator

  // empty iterator to be returned when there are no children
  private final static DTMAxisIterator EMPTY_ITERATOR =
      new DTMAxisIteratorBase() {
        public DTMAxisIterator reset() {
          return this;
        }

        public DTMAxisIterator setStartNode(int node) {
          return this;
        }

        public int next() {
          return DTM.NULL;
        }

        public void setMark() {
        }

        public void gotoMark() {
        }

        public int getLast() {
          return 0;
        }

        public int getPosition() {
          return 0;
        }

        public DTMAxisIterator cloneIterator() {
          return this;
        }

        public void setRestartable(boolean isRestartable) {
        }
      };


  // The root node id of the simple RTF
  public static final int RTF_ROOT = 0;

  // The Text node id of the simple RTF (simple RTF has only one Text node).
  public static final int RTF_TEXT = 1;

  // The number of nodes.
  public static final int NUMBER_OF_NODES = 2;

  // Document URI index, which increases by 1 at each getDocumentURI() call.
  private static int _documentURIIndex = 0;

  // Constant for empty String
  private static final String EMPTY_STR = "";

  // The String value of the Text node.
  // This is set at the endDocument() call.
  private String _text;

  // The array of Text items, which is built by the characters() call.
  // The characters() interface can be called multiple times. Each character item
  // can have different escape settings.
  protected String[] _textArray;

  // The DTMManager
  protected XSLTCDTMManager _dtmManager;

  // Number of character items
  protected int _size = 0;

  // The document ID
  private int _documentID;

  // A BitArray, each bit holding the escape setting for a character item.
  private BitArray _dontEscape = null;

  // The current escape setting
  private boolean _escaping = true;

  // Create a SimpleResultTreeImpl from a DTMManager and a document ID.
  public SimpleResultTreeImpl(XSLTCDTMManager dtmManager, int documentID) {
    _dtmManager = dtmManager;
    _documentID = documentID;
    _textArray = new String[4];
  }

  public DTMManagerDefault getDTMManager() {
    return _dtmManager;
  }

  // Return the document ID
  public int getDocument() {
    return _documentID;
  }

  // Return the String value of the RTF
  public String getStringValue() {
    return _text;
  }

  public DTMAxisIterator getIterator() {
    return new SingletonIterator(getDocument());
  }

  public DTMAxisIterator getChildren(final int node) {
    return new SimpleIterator().setStartNode(node);
  }

  public DTMAxisIterator getTypedChildren(final int type) {
    return new SimpleIterator(SimpleIterator.DIRECTION_DOWN, type);
  }

  // Return the axis iterator for a given axis.
  // The SimpleIterator is used for the child, descendant, parent and ancestor axes.
  public DTMAxisIterator getAxisIterator(final int axis) {
    switch (axis) {
      case Axis.CHILD:
      case Axis.DESCENDANT:
        return new SimpleIterator(SimpleIterator.DIRECTION_DOWN);
      case Axis.PARENT:
      case Axis.ANCESTOR:
        return new SimpleIterator(SimpleIterator.DIRECTION_UP);
      case Axis.ANCESTORORSELF:
        return (new SimpleIterator(SimpleIterator.DIRECTION_UP)).includeSelf();
      case Axis.DESCENDANTORSELF:
        return (new SimpleIterator(SimpleIterator.DIRECTION_DOWN)).includeSelf();
      case Axis.SELF:
        return new SingletonIterator();
      default:
        return EMPTY_ITERATOR;
    }
  }

  public DTMAxisIterator getTypedAxisIterator(final int axis, final int type) {
    switch (axis) {
      case Axis.CHILD:
      case Axis.DESCENDANT:
        return new SimpleIterator(SimpleIterator.DIRECTION_DOWN, type);
      case Axis.PARENT:
      case Axis.ANCESTOR:
        return new SimpleIterator(SimpleIterator.DIRECTION_UP, type);
      case Axis.ANCESTORORSELF:
        return (new SimpleIterator(SimpleIterator.DIRECTION_UP, type)).includeSelf();
      case Axis.DESCENDANTORSELF:
        return (new SimpleIterator(SimpleIterator.DIRECTION_DOWN, type)).includeSelf();
      case Axis.SELF:
        return new SingletonIterator(type);
      default:
        return EMPTY_ITERATOR;
    }
  }

  // %REVISIT% Can this one ever get used?
  public DTMAxisIterator getNthDescendant(int node, int n, boolean includeself) {
    return null;
  }

  public DTMAxisIterator getNamespaceAxisIterator(final int axis, final int ns) {
    return null;
  }

  // %REVISIT% Can this one ever get used?
  public DTMAxisIterator getNodeValueIterator(DTMAxisIterator iter, int returnType,
      String value, boolean op) {
    return null;
  }

  public DTMAxisIterator orderNodes(DTMAxisIterator source, int node) {
    return source;
  }

  public String getNodeName(final int node) {
    if (getNodeIdent(node) == RTF_TEXT) {
      return "#text";
    } else {
      return EMPTY_STR;
    }
  }

  public String getNodeNameX(final int node) {
    return EMPTY_STR;
  }

  public String getNamespaceName(final int node) {
    return EMPTY_STR;
  }

  // Return the expanded type id of a given node
  public int getExpandedTypeID(final int nodeHandle) {
    int nodeID = getNodeIdent(nodeHandle);
    if (nodeID == RTF_TEXT) {
      return DTM.TEXT_NODE;
    } else if (nodeID == RTF_ROOT) {
      return DTM.ROOT_NODE;
    } else {
      return DTM.NULL;
    }
  }

  public int getNamespaceType(final int node) {
    return 0;
  }

  public int getParent(final int nodeHandle) {
    int nodeID = getNodeIdent(nodeHandle);
    return (nodeID == RTF_TEXT) ? getNodeHandle(RTF_ROOT) : DTM.NULL;
  }

  public int getAttributeNode(final int gType, final int element) {
    return DTM.NULL;
  }

  public String getStringValueX(final int nodeHandle) {
    int nodeID = getNodeIdent(nodeHandle);
    if (nodeID == RTF_ROOT || nodeID == RTF_TEXT) {
      return _text;
    } else {
      return EMPTY_STR;
    }
  }

  public void copy(final int node, SerializationHandler handler)
      throws TransletException {
    characters(node, handler);
  }

  public void copy(DTMAxisIterator nodes, SerializationHandler handler)
      throws TransletException {
    int node;
    while ((node = nodes.next()) != DTM.NULL) {
      copy(node, handler);
    }
  }

  public String shallowCopy(final int node, SerializationHandler handler)
      throws TransletException {
    characters(node, handler);
    return null;
  }

  public boolean lessThan(final int node1, final int node2) {
    if (node1 == DTM.NULL) {
      return false;
    } else if (node2 == DTM.NULL) {
      return true;
    } else {
      return (node1 < node2);
    }
  }

  /**
   * Dispatch the character content of a node to an output handler.
   *
   * The escape setting should be taken care of when outputting to
   * a handler.
   */
  public void characters(final int node, SerializationHandler handler)
      throws TransletException {
    int nodeID = getNodeIdent(node);
    if (nodeID == RTF_ROOT || nodeID == RTF_TEXT) {
      boolean escapeBit = false;
      boolean oldEscapeSetting = false;

      try {
        for (int i = 0; i < _size; i++) {

          if (_dontEscape != null) {
            escapeBit = _dontEscape.getBit(i);
            if (escapeBit) {
              oldEscapeSetting = handler.setEscaping(false);
            }
          }

          handler.characters(_textArray[i]);

          if (escapeBit) {
            handler.setEscaping(oldEscapeSetting);
          }
        }
      } catch (SAXException e) {
        throw new TransletException(e);
      }
    }
  }

  // %REVISIT% Can the makeNode() and makeNodeList() interfaces ever get used?
  public Node makeNode(int index) {
    return null;
  }

  public Node makeNode(DTMAxisIterator iter) {
    return null;
  }

  public NodeList makeNodeList(int index) {
    return null;
  }

  public NodeList makeNodeList(DTMAxisIterator iter) {
    return null;
  }

  public String getLanguage(int node) {
    return null;
  }

  public int getSize() {
    return 2;
  }

  public String getDocumentURI(int node) {
    return "simple_rtf" + _documentURIIndex++;
  }

  public void setFilter(StripFilter filter) {
  }

  public void setupMapping(String[] names, String[] uris, int[] types, String[] namespaces) {
  }

  public boolean isElement(final int node) {
    return false;
  }

  public boolean isAttribute(final int node) {
    return false;
  }

  public String lookupNamespace(int node, String prefix)
      throws TransletException {
    return null;
  }

  /**
   * Return the node identity from a node handle.
   */
  public int getNodeIdent(final int nodehandle) {
    return (nodehandle != DTM.NULL) ? (nodehandle - _documentID) : DTM.NULL;
  }

  /**
   * Return the node handle from a node identity.
   */
  public int getNodeHandle(final int nodeId) {
    return (nodeId != DTM.NULL) ? (nodeId + _documentID) : DTM.NULL;
  }

  public DOM getResultTreeFrag(int initialSize, int rtfType) {
    return null;
  }

  public DOM getResultTreeFrag(int initialSize, int rtfType, boolean addToManager) {
    return null;
  }

  public SerializationHandler getOutputDomBuilder() {
    return this;
  }

  public int getNSType(int node) {
    return 0;
  }

  public String getUnparsedEntityURI(String name) {
    return null;
  }

  public Hashtable getElementsWithIDs() {
    return null;
  }

  /** Implementation of the SerializationHandler interfaces **/

  /**
   * We only need to override the endDocument, characters, and
   * setEscaping interfaces. A simple RTF does not have element
   * nodes. We do not need to touch startElement and endElement.
   */

  public void startDocument() throws SAXException {

  }

  public void endDocument() throws SAXException {
    // Set the String value when the document is built.
    if (_size == 1) {
      _text = _textArray[0];
    } else {
      StringBuffer buffer = new StringBuffer();
      for (int i = 0; i < _size; i++) {
        buffer.append(_textArray[i]);
      }
      _text = buffer.toString();
    }
  }

  public void characters(String str) throws SAXException {
    // Resize the text array if necessary
    if (_size >= _textArray.length) {
      String[] newTextArray = new String[_textArray.length * 2];
      System.arraycopy(_textArray, 0, newTextArray, 0, _textArray.length);
      _textArray = newTextArray;
    }

    // If the escape setting is false, set the corresponding bit in
    // the _dontEscape BitArray.
    if (!_escaping) {
      // The _dontEscape array is only created when needed.
      if (_dontEscape == null) {
        _dontEscape = new BitArray(8);
      }

      // Resize the _dontEscape array if necessary
      if (_size >= _dontEscape.size()) {
        _dontEscape.resize(_dontEscape.size() * 2);
      }

      _dontEscape.setBit(_size);
    }

    _textArray[_size++] = str;
  }

  public void characters(char[] ch, int offset, int length)
      throws SAXException {
    if (_size >= _textArray.length) {
      String[] newTextArray = new String[_textArray.length * 2];
      System.arraycopy(_textArray, 0, newTextArray, 0, _textArray.length);
      _textArray = newTextArray;
    }

    if (!_escaping) {
      if (_dontEscape == null) {
        _dontEscape = new BitArray(8);
      }

      if (_size >= _dontEscape.size()) {
        _dontEscape.resize(_dontEscape.size() * 2);
      }

      _dontEscape.setBit(_size);
    }

    _textArray[_size++] = new String(ch, offset, length);

  }

  public boolean setEscaping(boolean escape) throws SAXException {
    final boolean temp = _escaping;
    _escaping = escape;
    return temp;
  }

  /** Implementation of the DTM interfaces **/

  /**
   * The DTM interfaces are not used in this class. Implementing the DTM
   * interface is a requirement from MultiDOM. If we have a better way
   * of handling multiple documents, we can get rid of the DTM dependency.
   *
   * The following interfaces are just placeholders. The implementation
   * does not have an impact because they will not be used.
   */

  public void setFeature(String featureId, boolean state) {
  }

  public void setProperty(String property, Object value) {
  }

  public DTMAxisTraverser getAxisTraverser(final int axis) {
    return null;
  }

  public boolean hasChildNodes(int nodeHandle) {
    return (getNodeIdent(nodeHandle) == RTF_ROOT);
  }

  public int getFirstChild(int nodeHandle) {
    int nodeID = getNodeIdent(nodeHandle);
    if (nodeID == RTF_ROOT) {
      return getNodeHandle(RTF_TEXT);
    } else {
      return DTM.NULL;
    }
  }

  public int getLastChild(int nodeHandle) {
    return getFirstChild(nodeHandle);
  }

  public int getAttributeNode(int elementHandle, String namespaceURI, String name) {
    return DTM.NULL;
  }

  public int getFirstAttribute(int nodeHandle) {
    return DTM.NULL;
  }

  public int getFirstNamespaceNode(int nodeHandle, boolean inScope) {
    return DTM.NULL;
  }

  public int getNextSibling(int nodeHandle) {
    return DTM.NULL;
  }

  public int getPreviousSibling(int nodeHandle) {
    return DTM.NULL;
  }

  public int getNextAttribute(int nodeHandle) {
    return DTM.NULL;
  }

  public int getNextNamespaceNode(int baseHandle, int namespaceHandle,
      boolean inScope) {
    return DTM.NULL;
  }

  public int getOwnerDocument(int nodeHandle) {
    return getDocument();
  }

  public int getDocumentRoot(int nodeHandle) {
    return getDocument();
  }

  public XMLString getStringValue(int nodeHandle) {
    return new XMLStringDefault(getStringValueX(nodeHandle));
  }

  public int getStringValueChunkCount(int nodeHandle) {
    return 0;
  }

  public char[] getStringValueChunk(int nodeHandle, int chunkIndex,
      int[] startAndLen) {
    return null;
  }

  public int getExpandedTypeID(String namespace, String localName, int type) {
    return DTM.NULL;
  }

  public String getLocalNameFromExpandedNameID(int ExpandedNameID) {
    return EMPTY_STR;
  }

  public String getNamespaceFromExpandedNameID(int ExpandedNameID) {
    return EMPTY_STR;
  }

  public String getLocalName(int nodeHandle) {
    return EMPTY_STR;
  }

  public String getPrefix(int nodeHandle) {
    return null;
  }

  public String getNamespaceURI(int nodeHandle) {
    return EMPTY_STR;
  }

  public String getNodeValue(int nodeHandle) {
    return (getNodeIdent(nodeHandle) == RTF_TEXT) ? _text : null;
  }

  public short getNodeType(int nodeHandle) {
    int nodeID = getNodeIdent(nodeHandle);
    if (nodeID == RTF_TEXT) {
      return DTM.TEXT_NODE;
    } else if (nodeID == RTF_ROOT) {
      return DTM.ROOT_NODE;
    } else {
      return DTM.NULL;
    }

  }

  public short getLevel(int nodeHandle) {
    int nodeID = getNodeIdent(nodeHandle);
    if (nodeID == RTF_TEXT) {
      return 2;
    } else if (nodeID == RTF_ROOT) {
      return 1;
    } else {
      return DTM.NULL;
    }
  }

  public boolean isSupported(String feature, String version) {
    return false;
  }

  public String getDocumentBaseURI() {
    return EMPTY_STR;
  }

  public void setDocumentBaseURI(String baseURI) {
  }

  public String getDocumentSystemIdentifier(int nodeHandle) {
    return null;
  }

  public String getDocumentEncoding(int nodeHandle) {
    return null;
  }

  public String getDocumentStandalone(int nodeHandle) {
    return null;
  }

  public String getDocumentVersion(int documentHandle) {
    return null;
  }

  public boolean getDocumentAllDeclarationsProcessed() {
    return false;
  }

  public String getDocumentTypeDeclarationSystemIdentifier() {
    return null;
  }

  public String getDocumentTypeDeclarationPublicIdentifier() {
    return null;
  }

  public int getElementById(String elementId) {
    return DTM.NULL;
  }

  public boolean supportsPreStripping() {
    return false;
  }

  public boolean isNodeAfter(int firstNodeHandle, int secondNodeHandle) {
    return lessThan(firstNodeHandle, secondNodeHandle);
  }

  public boolean isCharacterElementContentWhitespace(int nodeHandle) {
    return false;
  }

  public boolean isDocumentAllDeclarationsProcessed(int documentHandle) {
    return false;
  }

  public boolean isAttributeSpecified(int attributeHandle) {
    return false;
  }

  public void dispatchCharactersEvents(
      int nodeHandle,
      org.xml.sax.ContentHandler ch,
      boolean normalize)
      throws org.xml.sax.SAXException {
  }

  public void dispatchToEvents(int nodeHandle, org.xml.sax.ContentHandler ch)
      throws org.xml.sax.SAXException {
  }

  public org.w3c.dom.Node getNode(int nodeHandle) {
    return makeNode(nodeHandle);
  }

  public boolean needsTwoThreads() {
    return false;
  }

  public org.xml.sax.ContentHandler getContentHandler() {
    return null;
  }

  public org.xml.sax.ext.LexicalHandler getLexicalHandler() {
    return null;
  }

  public org.xml.sax.EntityResolver getEntityResolver() {
    return null;
  }

  public org.xml.sax.DTDHandler getDTDHandler() {
    return null;
  }

  public org.xml.sax.ErrorHandler getErrorHandler() {
    return null;
  }

  public org.xml.sax.ext.DeclHandler getDeclHandler() {
    return null;
  }

  public void appendChild(int newChild, boolean clone, boolean cloneDepth) {
  }

  public void appendTextChild(String str) {
  }

  public SourceLocator getSourceLocatorFor(int node) {
    return null;
  }

  public void documentRegistration() {
  }

  public void documentRelease() {
  }

  public void migrateTo(DTMManager manager) {
  }
}
